package org.raphaello.loader;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import java.lang.reflect.Constructor;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import android.content.Context;
import android.util.Log;

import org.raphaello.Prototype.Raphaello;
import org.raphaello.core.Primitive;
import org.raphaello.core.objects.Object;
import org.raphaello.core.states.State;
import org.raphaello.core.jobs.Job;

public class Loader {
	private static final String TAG = "Loader";
	
	
	private enum PrimitiveType {
		OBJECT("object", "org.raphaello.core.objects."), 
		STATE("state", "org.raphaello.core.states."), 
		JOB("job", "org.raphaello.core.jobs.");
		
		private final String tagName;
		private final String packagePrefix;
		PrimitiveType(String tagName, String packagePrefix){
			this.tagName = tagName;
			this.packagePrefix = packagePrefix;
		}
		public String tagName() { return tagName; }
		public String packagePrefix() { return packagePrefix; }
		
	};
	
	
	/** Parse and create the object */
	@SuppressWarnings("unchecked")
	private void loadPrimitives(Document document, PrimitiveType primitiveType){
		
		NodeList primitives = document.getElementsByTagName(primitiveType.tagName());
		int numPrimitives = primitives.getLength();
		
		Log.d(TAG, primitiveType + " count=" + numPrimitives);
		
		
		for(int i = 0; i < numPrimitives; i++){
			// Assume that getElementByTagName returns Elements
			Element elem = (Element)primitives.item(i);
			
			
			
			String id = elem.getAttribute("id");
			String type = elem.getAttribute("type");
			String className = primitiveType.packagePrefix() + type;
			
			try{
				Log.d(TAG, "Creating a primitive=" + className);
				
				Class<? extends Primitive> primitiveClass = (Class<? extends Primitive>)Class.forName(className);
				Constructor<? extends Primitive> c = primitiveClass.getDeclaredConstructor(String.class);
				
				Log.d(TAG, "type=" + type + ", id=" + id);
				
				Primitive primitive = c.newInstance(id);
				
				// Adding the new object to the hash
				if(Raphaello.mPrimitives.containsKey(primitive.getId())){
	        		Log.e(TAG, "Already exist Id of primitive=" + primitive.getId());
	        	}
	        	else{
	        		Raphaello.mPrimitives.put(primitive.getId(), primitive);
	        		
	        		// Put the object to each hash of primitive types
	        		switch(primitiveType){
	        		case OBJECT:
	        			Raphaello.mObjects.put(primitive.getId(), (Object)primitive);
	        			break;
	        		case STATE:
	        			Raphaello.mStates.put(primitive.getId(), (State)primitive);
	        			break;
	        		case JOB:
	        			Raphaello.mJobs.put(primitive.getId(), (Job)primitive);
	        			break;
	        		}
	        	}
				
				// Adding the current primitive as a child of the parent primitive
				Element parentElem = null;
				String parentId = null;

				if(elem.getParentNode() != null && elem.getParentNode().getParentNode() != null){
					if(elem.getParentNode().getParentNode().getNodeType() == Node.ELEMENT_NODE){
						parentElem = (Element)elem.getParentNode().getParentNode();
						parentId = parentElem.getAttribute("id");
						
						if(Raphaello.mPrimitives.containsKey(parentId)){
							Raphaello.mPrimitives.get(parentId).addChild(primitive);
						}
						else{
							Log.e(TAG, "Parent Id(" + parentId + ") does not exist");
						}
					}
				}
				
				
    				
    			
    			
				
				
			}
			catch(Exception e){
				Log.e(TAG, "Can not create the primitive class=" + className + ", type=" + type + ", id=" + id);
				Log.e(TAG, "Exception message=" + e.getMessage());
				e.printStackTrace();
			}
			
			
		}
		
	}
	
	/** Parse all of properties from specified document and set itself to the object, state, and job */
	private void loadProperties(Document document){
		NodeList properties = document.getElementsByTagName("property");
		int numProperties = properties.getLength();
		
		Log.d(TAG, "property count=" + numProperties);
		
		for(int i = 0; i < numProperties; i++){
			// Assume that getElementByTagName returns Elements
			Element elem = (Element)properties.item(i);
			
			String name = elem.getAttribute("name");
			Log.d(TAG, "property name=" + name);
			
			String value = elem.getAttribute("value");
			Log.d(TAG, "property value=" + value);
			
			// Check the owner of this property
			Element owner = (Element)elem.getParentNode().getParentNode();
			String ownerId = owner.getAttribute("id");
			
			Log.d(TAG, "property onwer's id=" + ownerId);
		
			
			Primitive primitive = Raphaello.mPrimitives.get(ownerId);
			primitive.fillProperty(name, value);
			
		}
	}
	
	
	public Loader(Context context, String fileName){
		 // Load xml file
        try{
	        DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
	        docBuilderFactory.setIgnoringElementContentWhitespace(true);
	        
	        DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
	        
	        Document doc = docBuilder.parse(context.getResources().getAssets().open(fileName));
	        
	        // Load primitives and fill the properties
	        loadPrimitives(doc, PrimitiveType.OBJECT);
	        loadPrimitives(doc, PrimitiveType.STATE);
	        loadPrimitives(doc, PrimitiveType.JOB);
	        loadProperties(doc);
	        
	        
	
	        // Check all of primitives are valid or not
	        for(Primitive primitive : Raphaello.mPrimitives.values()){
	        	if(primitive.isValid() == false){
	        		Log.e(TAG, "Primitive id=" + primitive.getId() + " is invalid!");
	        	}
	        }
	        
        }
        catch(Exception e){
        	Log.e(TAG, "Can not init document builder");
        	e.printStackTrace();
        }
	}
	
	public static Element nodeToElement(Node node){
		if(node.getNodeType() == Node.ELEMENT_NODE){
			return (Element) node;
		}
		else{
			Log.e(TAG, "Passed argument is not an element node.");
			Log.e(TAG, "Passed node is " + node.getNodeType());
			
			return null;
		}
	}
	
	
}
